## Copyright 2010 Sindwiller ( sindwiller ayat gmail doooot com )

##    This program is free software: you can redistribute it and/or modify
##    it under the terms of the GNU General Public License as published by
##    the Free Software Foundation, either version 3 of the License, or
##    (at your option) any later version.
##
##    This program is distributed in the hope that it will be useful,
##    but WITHOUT ANY WARRANTY; without even the implied warranty of
##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##    GNU General Public License for more details.
##
##    You should have received a copy of the GNU General Public License
##    along with this program.  If not, see <http://www.gnu.org/licenses/>.


# Imports
import random, sys, os, math
import pygame
import time as pytime
from vec2d import *
from pygame.locals import *


# Constants
FRAMES_PER_SEC = 40
PLAYER_SPEED = 6
SCREENRECT = Rect(0, 0, 640, 480)
SCROLL_SPEED = 50
ENEMY_ODDS = 45
EXPLODE_TIME = 6

# Globals
class Img: pass # container for images!
score = 0

# Useful functions and classes

def load_image(file, transparent):
    """Load an image and shit."""
    file = os.path.join('data', file)
    try:
        surface = pygame.image.load(file)
    except pygame.error:
        raise SystemExit, 'Could not load "%s" %s'%(file, pygame.get_error())
    if transparent:
        corner = surface.get_at((0, 0))
        surface.set_colorkey(corner, RLEACCEL)

    return surface.convert()

class Debug():
    """An ugly debug class with some useful functions.
    I might replace this later on with something more decent,
    but for now, it's an ugly class.
    debug = Debug()
    debug.level_res()"""
    def __init__(self):
        self.posy = 0
        print "Debug class instanced!"

    def level_res(self):
        rows = 0
        columns = 0
        for x in range(0, SCREENRECT.width, Img.background.get_width()):
            columns += 1
            for y in xrange(int(-(self.posy%Img.background.get_height())), SCREENRECT.height, Img.background.get_height()):
                rows += 1
        print "Level resolution: %i rows x %i columns" % (rows, columns)

# Base classes

class Actor:
    """Actor class"""
    def __init__(self, image):
        self.image = image
        self.rect = image.get_rect()
        self.hostile = 0

    def update(self):
        """Update sprite state
        It's an empty function, jackass."""
        pass

    def draw(self, screen):
        screen.blit(self.image, self.rect)

    def erase(self, screen, background):
        screen.blit(background, self.rect, self.rect)

class Tileset():
    """A tileset class. Will probably be obsolete soon."""
    def __init__(self):
        self.dict = {}

    def __getitem__(self, key):
        return self.dict[key]

    def load(self, name):
        with open(os.path.join('data', name), 'r') as f:
            for line in f:
                # Split it alongside '=' instead
                self.dict[line.split('=')[0]] = load_image(line.split('=')[1].rstrip('\n'), 0)
##                self.dict[line[:1]] = load_image(line[2:].rstrip('\n'), 0)
        if f.closed:
            return True
        else:
            return False

class Tile():
    """Tile class"""
    def __init__(self, img, x, y):
        self.surface = img.copy()
        self.rect = Rect(x, y,
                         img.get_width(),
                         img.get_height())
        

class Level():
    def __init__(self, tileset):
        self.tileset = tileset
        self.posy = 0
        ## Use a list instead of a dict?
        self.tiles = []
        self.surface = pygame.Surface(SCREENRECT.size)

    def load(self, name):
        """ Load the level """
        # Variables for the coordinates of the tiles
        x = 0
        y = 480
        f = open(os.path.join('data', name), 'r')
        lines = f.readlines()
        # Get each line and handle it until we reach the end
        for line in lines:
            y -= 128
            if line != "":
                ## remove that newline char from there
                line = line.rstrip('\n')
                # Separate string alongside the whitespaces
                # Which results in a list
                nums = line.split(None)
                # Iterate over that list
                for n in nums:
                    self.tiles.append(Tile(self.tileset[n], x, y))
                    x += 128
                # Reset x in the end of the line loop
                # Reason:
            ##<Philip`> If you had 3 tiles per line, you were creating tiles at
            ##(0,480), (128,480), (256,480), (384, 352), ...
            ##<Philip`> so all the ones after the first line were probably
            ##off the right edge of the screen
                x = 0
            else:
                f.close()
                reading = 0
        return True

    def draw(self):
        for tile in self.tiles:
            tile.rect.move_ip(0, self.posy)
            self.surface.blit(tile.surface, (tile.rect.x,
                                             tile.rect.y))
        ## Move all rects by self.posy     




##class Background():
##    def __init__(self, bg):
##        self.surface = pygame.Surface(SCREENRECT.size)
##        self.rect = self.surface.get_rect()
##        self.posy = 0
##        self.draw(bg)
##        print "Background drawn"
##
##    def draw(self, bg):
##        for x in range(0, SCREENRECT.width, Img.background.get_width()):
##            for y in xrange(int(-(self.posy%Img.background.get_height())), SCREENRECT.height, Img.background.get_height()):
##                r = self.surface.blit(bg, (x, y))

class Weapon():
    def __init__(self):
        self.damage = 0
        self.name = ""

    def fire(self, screen):
        pass

# Derived game classes

class Laser(Weapon):
    def __init__(self):
        Weapon.__init__(self)
        self.damage = 1
        self.name = "Laser"

    def fire(self, screen, playerRect):
        surf = pygame.Surface((SCREENRECT.width, SCREENRECT.height), SRCALPHA)
        surf.set_alpha(255)
        rect = pygame.draw.line(surf, (0, 0, 0), playerRect.center, (playerRect.centerx, 0))
        screen.blit(surf, (0, 0))
        return rect

class Explosion(Actor):
    def __init__(self, actor):
        Actor.__init__(self, Img.explosion)
        self.life = EXPLODE_TIME
        self.rect.center = actor.rect.center

    def update(self):
        self.life = self.life - 1

class Player(Actor):
    def __init__(self):
        Actor.__init__(self, Img.player)
        self.facing = Vector2D(0, -1)
        self.alive = 1
        self.life = 10
        self.reloading = 0
        self.rect.centerx = SCREENRECT.centerx
        self.rect.bottom = SCREENRECT.bottom - 10
        self.weapons = {Laser().name: Laser()}
        self.currentWeapon = self.weapons['Laser'].name
        
    def move(self, direction_x, direction_y):
        self.rect = self.rect.move(direction_x*PLAYER_SPEED, direction_y*PLAYER_SPEED).clamp(SCREENRECT)

    def fire(self, screen):
        rect = self.weapons[self.currentWeapon].fire(screen, self.rect)
        return rect

class Enemy(Actor):
    def __init__(self, image):
        Actor.__init__(self, image)
        self.life = 3
        self.hostile = 1
        self.facing = Vector2D(0, 1)
        self.rect.inflate_ip(3, 3)
        self.maxSpeed = 15
        self.rect.center = (random.randint(0, SCREENRECT.width), 15)

    def update(self, player):
        ## Move to player but keep distance to him
        distV = Vector2D(player.rect.x - self.rect.x, player.rect.y - self.rect.y)
        if distV.length() != 0:
            dirV = distV.normalize()
        moveV = Vector2D(8, 8)
        if distV > 5:            
            self.rect = self.rect.move(moveV.x * dirV.x, moveV.y * dirV.y)
        
    
def main():

    global score
    # Initialisation and stuff
    pygame.init()
    pygame.font.init()
    screen = pygame.display.set_mode(SCREENRECT.size, 0)
    clock = pygame.time.Clock()

    # Load level and level essentials

    # Will probably remove Tileset() once I add
    # more structure to the level files
    tileset = Tileset()

    # Didn't write this with 'try, except' because I don't understand
    # error handling quite well yet

    if tileset.load('tileset1'):
        print "Tileset loaded."
    else:
        print "Tileset could not be loaded."
        pygame.quit()

    level = Level(tileset)
        
    if level.load('level1'):
        print "Level loaded."
    else:
        print "An error occured when loading the level."
        pygame.quit()
    
    

    # Load the Resources
    Img.player = load_image('player.png', 1)
##    Img.background = load_image('grass.jpg', 0)
    Img.enemy = load_image('enemy.png', 1)
    Img.explosion = load_image('explosion1.gif', 1)

    # Initialize actors
    player = Player()
    enemy = Enemy(Img.enemy)
    enemies = [enemy]
    explosions = []

    # Initialize HUD
    font = pygame.font.Font(None, 40)
    scoreboard_rect = Rect(20, 20, 100, 40)
    currentWeapon_rect = Rect(20, 60, 100, 40)
    life_rect = Rect(20, 100, 100, 40)


    while player.alive or explosions:
        # Clock
        time = clock.tick(FRAMES_PER_SEC)

        # Gather Events
        pygame.event.pump()
        keystate = pygame.key.get_pressed()
        if keystate[K_ESCAPE] or pygame.event.peek(QUIT):
            pygame.quit()
            break
            

        level.draw()
        screen.blit(level.surface, (0, 0))

        
        # Spawn random enemies (for now)
        if not int(random.random() * ENEMY_ODDS):
            enemies.append(Enemy(Img.enemy))

        
        # Clear screen and update actors
        for actor in [player] + enemies + explosions:
            actor.erase(screen, level.surface)
            if actor.hostile:
                actor.update(player)
            else:
                actor.update()

        # Clear explosions

        for e in explosions:
            if e.life == 0:
                explosions.remove(e)
        
        # Move the player
        direction_x = keystate[K_d] - keystate[K_a]
        direction_y = keystate[K_s] - keystate[K_w]
        player.move(direction_x, direction_y)
        # Move the background
        level.posy  = (time/1000.) * SCROLL_SPEED

        # Get bounding boxes for enemies
        erects = []
        for e in enemies:
            erects.append(e.rect)
        
##        # Detect collision between enemies (really inefficient)
##        for rect in erects + [player.rect]:
##            index = rect.collidelist(erects)
##            if index != -1:
##                v = Vector2D(erects[index].x - rect.x, erects[index].y - rect.y)
##                rect.move_ip(v.x, v.y)
##                erects[index].move_ip(-v.x, -v.y)

        # Detect collision between enemies and player
        col = player.rect.collidelist(erects)
        if col != -1:
            e = enemies[col]
            explosions.append(Explosion(e))
##            enemies.remove(e)
            del enemies[col]
            erects.remove(e.rect)
            player.life -= 1
            if player.life == 0:
                player.alive = 0
        


        # Shoot and - if he hits - DIE DIE DIE
        # Changed this from K_SPACE to K_RETURN
        if not player.reloading and keystate[K_RETURN]:
            laser_rect = player.fire(screen)
            hit = laser_rect.collidelist(erects)
            if hit != -1:
                e = enemies[hit]
                e.life -= 1
                if e.life == 0 and e in enemies:
##                    enemies.remove(enemy)
                    del enemies[hit]
                    erects.remove(e.rect)
                    score += 50
                    explosions.append(Explosion(e))
        player.reloading = keystate[K_RETURN]

        
        # Draw all actors
        for actor in [player] + enemies + explosions:
            actor.draw(screen)

        # Draw HUD elements
        scoreboard = font.render(str(score), 1, (0, 0, 0))
        currentWeapon = font.render(player.currentWeapon, 1, (0, 0, 0))
        lifeBar = font.render(str(player.life), 1, (0, 0, 0))
        screen.blit(scoreboard, scoreboard_rect)
        screen.blit(currentWeapon, currentWeapon_rect)
        screen.blit(lifeBar, life_rect)

        pygame.display.flip()

    # Once player is dead, get out of loop and quit
    pygame.quit()
    


if __name__ == '__main__':
    main()

